package cz.data.platform.web;

import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import cz.data.common.base.BaseResponse;
import cz.data.common.exception.AuthException;
import cz.data.common.exception.DataException;
import cz.data.platform.web.utils.RequestHolder;
import feign.RetryableException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(DataException.class)
    public BaseResponse handleWithException(DataException e) {
        log.error(String.format("自定义异常信息 ex=%s", e.getMessage()), e);
        return BaseResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
    }

    @ExceptionHandler(RetryableException.class)
    public BaseResponse handleRetryableException(RetryableException e) {
        log.warn(String.format("远程调用错误 ex=%s", e.getMessage()), e);
        return BaseResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "远程服务错误! 请稍后重试");
    }

    /**
     * 请求的 JSON 参数在请求体内的参数校验
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(","));
        log.warn("参数校验异常信息 ex={}", e.getMessage());
        return BaseResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }

    /**
     * 请求的 URL 参数检验
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public BaseResponse handleConstraintViolationException(ConstraintViolationException e) {
        String message = e.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(","));
        log.warn("参数校验异常信息 ex={}", e.getMessage());
        return BaseResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public BaseResponse httpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("http参数转化错误: {}", e.getMessage());
        String message;
        if (e.contains(InvalidFormatException.class)) {
            message = "参数格式错误";
        } else {
            message = e.getMessage();
        }
        return BaseResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }

    @ExceptionHandler(Exception.class)
    public BaseResponse handleException(Exception e) {
        log.error(String.format("自定义异常信息 ex=%s", e.getMessage()), e);
        return BaseResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
    }

    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(AuthException.class)
    public BaseResponse handleAuthException(AuthException e) {
        return BaseResponse.error(HttpStatus.UNAUTHORIZED.value(), e.getMessage());
    }

    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public Object handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) throws IOException {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(requestAttributes))
            return null;
        HttpServletResponse response = RequestHolder.getHttpServletResponse();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        if (StringUtils.equals(accept, MediaType.TEXT_EVENT_STREAM_VALUE)){
            //SSE请求不需要返回并且指定content-type
            response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
            return null;
        }
        //其余情况返回JSON
        return handleException(e);
    }
}